/*global FBL */
FBL.ns(function () {

	with (FBL) {

		const Cc = Components.classes; 
		const Ci = Components.interfaces; 
		
		const FirebugPrefDomain = "extensions.firebug"; 
		
//		const observerService = CCSV("@mozilla.org/observer-service;1", "nsIObserverService");
		
		// FireLinkReport preferences
		const showAllLinksAsAbsolutePref = "firelinkreport.showAllLinksAsAbsolute";
		const showOnlyLinksToWebPagesPref = "firelinkreport.showOnlyLinksToWebPages";
		const showLinksToOtherPartsOfSamePagePref = "firelinkreport.showLinksToOtherPartsOfSamePage";
		const lastSortedColumn = "firelinkreport.lastSortedColumn";
		 
		// Preferences
		const PrefService = Cc["@mozilla.org/preferences-service;1"];
		const nsIPrefService = Ci.nsIPrefService;
		const nsIPrefBranch = Ci.nsIPrefBranch; 
		const nsIPrefBranch2 = Ci.nsIPrefBranch2;
		const prefService = PrefService.getService(nsIPrefService);
		const prefs = PrefService.getService(nsIPrefBranch2);
		
		 
		const panelName = "firelinkreport";
		
		var BaseModule = Firebug.ActivableModule ? Firebug.ActivableModule : Firebug.Module; 
		
		Firebug.FireLinkReportModel = extend(BaseModule, {

//		    observersRegistered: false, 
		    
		    /**
		     * Initialization of the module.
		     * Firefox window. There is a new instance of the module for each Firefox
		     * winodow that has Firebug embedded.
		     *
		     * @param {String} prefDomain Preference domain (e.g. extensions.firebug)
		     * @param {String} prefNames Default Firebug preference array.
		     */
		    initialize: function(prefDomain, prefNames) 
		    {
		        this.panelName = panelName;
		        
		        BaseModule.initialize.apply(this, arguments);

		        // All the necessary observers are registered by default. Even if the 
		        // panel can be disabled (entirely or for a specific host) there is
		        // no simple way to find out this now, as the context isn't available. 
		        // All will be unregistered again in the initContext (if necessary).
		        // There is no big overhead, the initContext is called just after the
		        // first document request.
//		        this.registerObservers(null);

		    },

//		    registerObservers: function(context) {
//		        if (this.observersRegistered) {
//		            return;
//		        }
//
//		        observerService.addObserver(HttpObserver, "http-on-modify-request", false);
//		        observerService.addObserver(HttpObserver, "http-on-examine-response", false);
//
//		        this.observersRegistered = true;
//		    },
//		 			
//		    unregisterObservers: function(context) {
//		        if (!this.observersRegistered) {
//		            return;
//		        }
//
//		        observerService.removeObserver(HttpObserver, "http-on-modify-request");
//		        observerService.removeObserver(HttpObserver, "http-on-examine-response");
//
//		        this.observersRegistered = false;
//		    }, 
//		    
//		    shutdown: function() {
//		        this.unregisterObservers(null);
//		    },
		    
		    showPanel: function(browser, panel) {
		    	var isPanel = panel && panel.name === panelName;

		    	// Firebug 1.4, chrome changes.
		        var chrome = browser.chrome ? browser.chrome : Firebug.chrome;
		    	var fireLinkReportButtons = chrome.$("fbFireLinkReportButtons");
				collapse(fireLinkReportButtons, !isPanel);
		    },
		
			addStyleSheet : function (doc) {
				var styleSheet = document.getElementById("firelinkreport-style");
				if (!styleSheet) {
					styleSheet = createStyleSheet(doc, "chrome://firelinkreport/skin/firelinkreport.css");
					styleSheet.id = "firelinkreport-style";
					addStyleSheet(doc, styleSheet);
				}
			},
			
		    /**
		     * Called by the framework when a context is created for Firefox tab.
		     * 
		     *  @param {Firebug.TabContext} Context for the current Firefox tab.
		     */
		    initContext: function(context)
		    {
		        // The base class must be called after the context for Cookies panel is 
		        // properly initialized. The panel can be created inside this function
		        // (within Firebug.ActivableModule.enablePanel), which can result in
		        // calling FireCookiePanel.initialize method. This method directly calls
		        // FireCookiePanel.refresh, which needs the context.cookies object ready.
		        BaseModule.initContext.apply(this, arguments);

//		        // Unregister all observers if the panel is disabled.
//		        if (!this.isEnabled(context)) {
//		        	this.unregisterObservers(context);
//		        }
		    },
		    
		    reattachContext: function(browser, context) {
		        BaseModule.reattachContext.apply(this, arguments);

		        // The context isn't available if FB is disabled.
		        if (!context)
		            return;

		        // xxxHonza the panel is created here, it's overhead.
		        // however the stylesheet must be appended here and the list of cookies
		        // mus be refreshed otherwise, the event handlers doesn't work
		        // not sure where exactly is the bug.
		        var panel = context.getPanel(panelName);

		        // Add styles into the panel HTML document.
		        if (browser.detached)
		            this.addStyleSheet(panel);

		        // From some reason, Firebug doesn't set the ownerPanel to the panel
		        // node element. (it's properly set once the page is reloaded, but no the first time)
		        // The Firebug.getElementPanel method doesn't work then. 
		        // This is fixed in Firebug 1.2 (the ownerPanel is set in Initialize & reattach methods)
		        panel.panelNode.ownerPanel = panel;

		        // Refresh panel. From some reason, if FB UI is detached, all event 
		        // listeners (e.g. onClick handlers registered in domplate template) 
		        // are somwhos damaged and not called. 
		        // Workaround - if the panel is refreshed event handlers work.
		        //
		        // See bug http://code.google.com/p/fbug/issues/detail?id=724, console
		        // has the same problem. However it can't be simply refreshed.
		        // OK, this bug should be fixed (R735) since FB 1.2b4
		        //panel.refresh();
		    },

		    destroyContext: function(context) {
		        BaseModule.destroyContext.apply(this, arguments);
		    },
		 		    
		    watchWindow: function(context, win) {
		        context.window.addEventListener("beforeunload", this.onBeforeUnload, false);
//		        context.window.addEventListener("load", function() {
//		        	alert('load');
//		        	this.onRun(context);
//		        }, false);
		        context.window.addEventListener("load", this.onLoad, false);
//			    context.window.addEventListener("load", function () {
//					gBrowser.addEventListener("load", this.onLoad, false);
//				}, false);
		    },

			onLoad: function(event) {
		        var view = event.target.defaultView;
		        var context = TabWatcher.getContextByWindow(view);

		        if (!context) {
		            return;
		        }
		        
		        var panel = context.getPanel(panelName, true);
		        if (panel) {
		            panel.refresh();
		        }

		    },
		    
		    onBeforeUnload: function(event) {
		        var view = event.target.defaultView;
		        var context = TabWatcher.getContextByWindow(view);
		        if (!context) {
		            return;
		        }

		        var panel = context.getPanel(panelName, true);
		        if (panel) {
		            panel.clear();
		        }
		    },
		    
		    onPanelActivate: function(context, init, activatedPanelName) {
		        if (activatedPanelName != panelName)
		            return;

//		        this.registerObservers(context);

		        $("fbStatusIcon").setAttribute(panelName, "on");

		        context.invalidatePanels(panelName);

		        var panel = context.getPanel(panelName, true);
		        
		        if (panel)
		            panel.showToolbarButtons("fbFireLinkReportButtons", true);
		    }, 		    

//		    onPanelDeactivate: function(context, destroy, activatedPanelName) {
//		        this.unregisterObservers(context);
//		    },

		    onLastPanelDeactivate: function(context, destroy) {
		        $("fbStatusIcon").removeAttribute(panelName);
		    },

		    onSuspendFirebug: function(context) {
//		        if (context) {
//		            // Firebug 1.3
//		            this.unregisterObservers(context);
//		        }
//		        else
//		        {
//		            // Firebug 1.4 (context parameter doesn't exist since 1.4)
//		            // Suspend only if enabled.
//		            if (Firebug.FireLinkReportModel.isAlwaysEnabled())
//		                TabWatcher.iterateContexts(Firebug.FireLinkReportModel.unregisterObservers);
//		        }

		        $("fbStatusIcon").removeAttribute(panelName);
		    },

		    onResumeFirebug: function(context)
		    {
		        if (context)
		        {
//		            // Firebug 1.3
//		            this.registerObservers(context);

		            if (Firebug.FireLinkReportModel.isEnabled(context))
		                $("fbStatusIcon").setAttribute(panelName, "on");
		        }
		        else
		        {
//		            // Firebug 1.4 (context parameter doesn't exist since 1.4)
//		            if (Firebug.FireLinkReportModel.isAlwaysEnabled())
//		                TabWatcher.iterateContexts(Firebug.FireLinkReportModel.registerObservers);

		            $("fbStatusIcon").setAttribute(panelName, "on");
		        }
		    },

//		    onEnabled: function(context)
//		    {
//		    	this.registerObservers(context);
//		    },
//
//		    onDisabled: function(context)
//		    {
//		        this.unregisterObservers(context);
//		    },

		    isEnabled: function(context)
		    {
		        // For backward compatibility with Firebug 1.1. ActivableModule has been
		        // introduced in Firebug 1.2.
		        if (!Firebug.ActivableModule)
		            return true;

		        return BaseModule.isEnabled.apply(this, arguments);
		    },	

		    onEnablePrefChange: function(pref)
		    {
		        BaseModule.onEnablePrefChange.apply(this, arguments);
		    },
		    

			onRefresh: function (context) {
		    	if (!context) return false;

		        var panel = context.getPanel(panelName, true);
		        if (panel) {
		        	panel.refresh();
		        }
		    },
		
		    onToggleFilter: function(context, category, filter) {
		        setPref(FirebugPrefDomain, 'firelinkreport.filter.' + category, filter); 
		        
		        var panel = context.getPanel(panelName, true);
		        if (panel) {
		        	panel.filter();
		        }
		    },
		    
		    onHelp: function(context) {
		        openNewTab("http://www.softwarecocktail.com/projects/firelinkreport/");
		    },

		    onAbout: function(context) {
		        var parent = context.chrome.window;
		        var extensionManager = CCSV("@mozilla.org/extensions/manager;1", "nsIExtensionManager"); 
		        parent.openDialog("chrome://mozapps/content/extensions/about.xul", "",
		            "chrome,centerscreen,modal", "urn:mozilla:item:firelinkreport@softwarecocktail.com",
		            extensionManager.datasource);
		    }, 		    
		    
		});
			

		// Localization
		//-----------------------------------------------------------------------------

		/**
		 * Use this function to translate a string.
		 * @param {String} name Specifies a string key within firelinkreport.properties file.
		 */
		function $FLR_STR(name) {
		    try
		    {
		        return document.getElementById("strings_firelinkreport").getString(name.replace(' ', '_', "g"));
		    }
		    catch (err)
		    {
		    	if (FBTrace.DBG_FIRELINKREPORT)
		        {
		            FBTrace.sysout("firelinkreport.Missing translation for: " + name + "\n");
		            FBTrace.sysout("firelinkreport.getString FAILS ", err);
		        }
		    }

		    // Use only the label after last dot.
		    var index = name.lastIndexOf(".");
		    if (index > 0)
		        name = name.substr(index + 1);

		    return name;
		}

		function $FLR_STRF(name, args)
		{
		    try
		    {
		        return document.getElementById("strings_firelinkreport").getFormattedString(name.replace(' ', '_', "g"), args);
		    }
		    catch (err)
		    {
		        if (FBTrace.DBG_FIRELINKREPORT)
		        {
		            FBTrace.sysout("firelinkreport.Missing translation for: " + name + "\n");
		            FBTrace.sysout("firelinkreport.getString FAILS ", err);
		        }
		    }

		    // Use only the label after last dot.
		    var index = name.lastIndexOf(".");
		    if (index > 0)
		        name = name.substr(index + 1);

		    return name;
		}

//		function fcInternationalize(element, attr, args)
//		{
//		    if (typeof(element) == "string")
//		        element = document.getElementById(element);
//
//		    var xulString = element.getAttribute(attr);
//		    var localized = args ? $FLR_STRF(xulString, args) : $FLR_STR(xulString);
//
//		    // Set localized value of the attribute.
//		    element.setAttribute(attr, localized);
//		} 

		
		function FireLinkReportPanel () {
			
		}
		
		// Firebug.AblePanel has been renamed in Firebug 1.4 to ActivablePanel.
		var BasePanel = Firebug.AblePanel ? Firebug.AblePanel : Firebug.Panel;
		BasePanel = Firebug.ActivablePanel ? Firebug.ActivablePanel : BasePanel; 
		
		FireLinkReportPanel.prototype = extend(BasePanel, {
			name : panelName,
			title : $FLR_STR("firelinkreport.links"),
		    searchable: true, 
		    initialize : function () {

				var hcr = HeaderColumnResizer;
				this.onMouseClick = bind(hcr.onMouseClick, hcr);
				this.onMouseDown = bind(hcr.onMouseDown, hcr);
				this.onMouseMove = bind(hcr.onMouseMove, hcr);
				this.onMouseUp = bind(hcr.onMouseUp, hcr);
				this.onMouseOut = bind(hcr.onMouseOut, hcr); 
			
		        BasePanel.initialize.apply(this, arguments);
				Firebug.FireLinkReportModel.addStyleSheet(this.document);
		        
				this.refresh(); 
			},
			
	    refresh: function() {
			if (!Firebug.FireLinkReportModel.isEnabled(this.context))
	            return;

	        // Links are displayed only for web pages.
	        var location = this.context.window.location;
	        if (!location)
	            return;

	        var protocol = location.protocol;
	        if (protocol.indexOf("http") != 0)
	            return;
	        
	        this.linkList = this.getLinkList();
	        
//	        alert(links.length);
			
	        this.filter();
	    },
	    
	    filter: function() {
	    	if (!this.linkList || this.linkList.length == 0) {
	    		this.refresh();
	    	}
	    	
	    	this.clear();

	        // Create link list table.
	        this.table = Templates.LinkTable.createTable(this.panelNode);

	    	if (this.linkList.length) {
		        internalexternalFilter = getPref(FirebugPrefDomain, 'firelinkreport.filter.internalexternal');
		        absoluterelativeFilter = getPref(FirebugPrefDomain, 'firelinkreport.filter.absoluterelative');
		        
		        var filteredLinks = new Array();

		    	for (var i = 0; i < this.linkList.length; i++) {
	            	var filteredLink = this.linkList[i];
	            	
	            	if ((internalexternalFilter === 'internalexternal' || 
	            			(internalexternalFilter === 'internal' && filteredLink.external === false) ||
	            			(internalexternalFilter === 'external' && filteredLink.external === true)) &&
	            			(absoluterelativeFilter === 'absoluterelative' || 
	            			(absoluterelativeFilter === 'absolute' && filteredLink.absolute === true) ||
	            			(absoluterelativeFilter === 'relative' && filteredLink.absolute === false))) {
	            			
	            		filteredLinks.push(filteredLink);
		    		}
	            }
		    	
		        // Generate HTML list of links using domplate.
		        if (filteredLinks.length)
		        {
		            var header = getElementByClass(this.table, "linkHeaderRow");
		            var tag = Templates.LinkRow.linkTag;
		            var row = tag.insertRows({links: filteredLinks}, header)[0];

		            for (var i = 0; i < filteredLinks.length; i++)
		            {
		                var link = filteredLinks[i];
		                link.row = row;
		                row.repObject = link;
		                row = row.nextSibling;
		            }
		        }
	    	}

	        // Sort automatically the last sorted column. The preference stores
	        // two things: name of the sorted column and sort direction asc|desc.
	        // Example: colExpires asc
	        var prefValue = getPref(FirebugPrefDomain, lastSortedColumn);
	        if (prefValue) {
	        	var values = prefValue.split(" ");
	            Templates.LinkTable.sortColumn(this.table, values[0], values[1]);
	        }

	    },

	    initializeNode: function(oldPanelNode) {
	        // Register event handlers for table column resizing.
	        this.document.addEventListener("click", this.onMouseClick, true);
	        this.document.addEventListener("mousedown", this.onMouseDown, true);
	        this.document.addEventListener("mousemove", this.onMouseMove, true);
	        this.document.addEventListener("mouseup", this.onMouseUp, true);
	        this.document.addEventListener("mouseout", this.onMouseOut, true);
	    },

	    destroyNode: function() {
	        this.document.removeEventListener("click", this.onMouseClick, true);
	        this.document.removeEventListener("mousedown", this.onMouseDown, true);
	        this.document.removeEventListener("mousemove", this.onMouseMove, true);
	        this.document.removeEventListener("mouseup", this.onMouseUp, true);
	        this.document.removeEventListener("mouseout", this.onMouseOut, true);
	    },

	    detach: function(oldChrome, newChrome)
	    {
	        BasePanel.detach.apply(this, arguments);
	    },

	    reattach: function(doc)
	    {
	        BasePanel.reattach.apply(this, arguments);
	    },

	    clear: function() {
	        if (this.panelNode)
	            clearNode(this.panelNode);
	            
	        this.table = null;
	    },

	    show: function(state)
	    {
	    	// For backward compatibility with Firebug 1.1
	        if (Firebug.ActivableModule)
	        {
	            var shouldShow = Firebug.FireLinkReportModel.isEnabled(this.context);
	            this.showToolbarButtons("fbFireLinkReportButtons", shouldShow);
	            if (!shouldShow)
	            {
	                // The activation model has been changed in Firebug 1.4. This is 
	                // just to keep backward compatibility.
	                if (Firebug.DisabledPanelPage.show)
	                    Firebug.DisabledPanelPage.show(this, Firebug.FireLinkReportModel);
	                else
	                    Firebug.FireLinkReportModel.disabledPanelPage.show(this);
	                return;
	            }
	        }
	    },

	    hide: function()
	    {
	    },

	    getOptionsMenuItems: function(context) {

	        return [
	                MenuUtils.optionMenuAndRefresh(context, "firelinkreport.showAllLinksAsAbsolute",
	                		FirebugPrefDomain, showAllLinksAsAbsolutePref),
	                MenuUtils.optionMenuAndRefresh(context, "firelinkreport.showOnlyLinksToWebPages",
	                		FirebugPrefDomain, showOnlyLinksToWebPagesPref),
	                MenuUtils.optionMenuAndRefresh(context, "firelinkreport.showLinksToOtherPartsOfSamePage",
	                		FirebugPrefDomain, showLinksToOtherPartsOfSamePagePref)
	            ]; 	    	
	    },
	    
	    optionMenu: function(context, label, domain, option) {
	        var value = getPref(domain, option);
	        return { label: $FLR_STR(label), nol10n: true, type: "checkbox", checked: value,
	            command: bindFixed(setPref, this, domain, option, !value) };
	    },
	    
	    getContextMenuItems: function(object, target)
	    {
	        var items = [];

	        // If the user clicked at a link row, the context menu is already
	        // initialized and so, bail out.
	        var linkRow = getAncestorByClass(target, "linkRow");
	        if (linkRow)
	            return items;

	        // Make sure default items (cmd_copy) is removed.
	        Templates.Rep.getContextMenuItems.apply(this, arguments); 
	        
	        return items;
	    },

	    search: function(text)
	    {
	        if (!text)
	            return;

	        // Make previously visible nodes invisible again
	        if (this.matchSet)
	        {
	            for (var i in this.matchSet)
	                removeClass(this.matchSet[i], "matched");
	        }

	        this.matchSet = [];

	        function findRow(node) { return getAncestorByClass(node, "linkRow"); }
	        var search = new TextSearch(this.panelNode, findRow);

	        var linkRow = search.find(text);
	        if (!linkRow)
	            return false;

	        for (; linkRow; linkRow = search.findNext())
	        {
	            setClass(linkRow, "matched");
	            this.matchSet.push(linkRow);
	        }

	        return true;
	    },

	    getPopupObject: function(target)
	    {
	        var header = getAncestorByClass(target, "linkHeaderRow");
	        if (header)
	            return Templates.LinkTable;
	            
	        return BasePanel.getPopupObject.apply(this, arguments);
	    },

	    findRepObject: function(link) {
	        var row = getElementByClass(this.panelNode, "linkRow");
	        
	        while (row) {
	            var rep = row.repObject;
	            if (rep) {
	                if (rep.link.absoluteHref == link.absoluteHref &&
	                    rep.link.title == link.title &&
	                    rep.link.text == link.text) {
	                    return rep;
	                }
	            }

	            row = row.nextSibling;
	        }

	        return null;
	    },
	    
	    getDocumentList: function(frame) {
	        var documents = new Array();

	        if (frame) {
	    		var frames = frame.frames;
	    		var framesLength = frames.length;

	    		if (frame.document) {
	    			documents.push(frame.document);
	    		}
	    		
	    		for (var i = 0; i < framesLength; i++) {
	    			documents = documents.concat(this.getDocumentList(frames[i]));
	    		}
	    	}
	        
	        return documents;
	    	
	    },
	    
	    highlightHrefElement: function(href) {
	    	var styleClassName = "firelinkreport-highlighted";
	    	
	    	var browser = window.top.getBrowser().selectedBrowser.contentWindow;
	    	var documentList = this.getDocumentList(browser);

	    	var firstElementShown = false;
	    	
	    	// Create style if doesn't exist
	    	this.createStyles();
	    	
	    	// Remove previously highlighted link
	    	for (var i = 0; i < documentList.length; i++) {
		    	var allElements = documentList[i].getElementsByClassName(styleClassName);

		    	for (var j = 0; j < allElements.length; j++) {
					var element = allElements[j];
					element.className = element.className.replace(styleClassName, "");
				}
	    	}
	    	
	    	
	    	for (var i = 0; i < documentList.length; i++) {
		    	var allElements = documentList[i].getElementsByTagName("a");

				for (var j = 0; j < allElements.length; j++) {
					element = allElements[j];
					if (element.nodeType === 1) {
						hrefAttr = element.getAttribute("href");
						if (hrefAttr == href) {
							element.className += " " + styleClassName;
							
							if (!firstElementShown) {
								firstElementShown = true;
								element.scrollIntoView(true);
								if (!this.isElementVisible(documentList[i], element)) {
									alert('This link doesn\'t seem to be visible on the page at the moment.');
								}
							}
							
						}
					}
				}
				
	    	}
	    },
	    
	    isElementVisible: function(document, element) {
	    	  if (element.offsetWidth === 0 || element.offsetHeight === 0) return false;
	    	  return true;
	    },
	    
	    makeElementVisible: function(document, element) {
	    	var visible = isElementVisible(document, element);
	    	
	    	if (!visible && element) {
	    		element.hidden = false;
	    	}
	    },
	    
	    createStyles: function() {
	    	var browser = window.top.getBrowser().selectedBrowser.contentWindow;
	    	var documentList = this.getDocumentList(browser);
	    	
	    	// Check if style sheet has already been created
	    	for (var i = 0; i < documentList.length; i++) {
		    	var styleSheetElement = documentList[i].getElementById("firelinkreport-style");
		    	if (styleSheetElement) {
		    		return;
		    	}
	    	}

	    	// If style doesn't then create it in each frame
	    	for (var i = 0; i < documentList.length; i++) {
		    	var styleSheetElement = documentList[i].createElement("style");
		    	styleSheetElement.id = "firelinkreport-style";
		    	styleSheetElement.type = "text/css";
		    	
		    	var headElements = documentList[i].getElementsByTagName("head");
		    	if (!headElements) {
		    		var headElement = documentList[i].createElement("head");
		    		documentList[i].getElementsByTagName("html")[0].appendChild(headElement); 
		    	}
		    	
		    	documentList[i].getElementsByTagName("head")[0].appendChild(styleSheetElement);
		    	
				// Apply CSS rules
				var allStyleSheets = documentList[i].styleSheets;
				var lastStyleSheet = allStyleSheets.length - 1;
				allStyleSheets[lastStyleSheet].insertRule(".firelinkreport-highlighted { border: 1px solid red !important; }", 0);
	    	}
			
	    },
	    
	    getLinkList: function() {
			var browser = window.top.getBrowser().selectedBrowser.contentWindow;
	    	var documentList = this.getDocumentList(browser);
	    	
	    	var linkList = new Array();
	    	
	    	var showOnlyLinksToWebPages = getPref(FirebugPrefDomain, showOnlyLinksToWebPagesPref);
	    	var showLinksToOtherPartsOfSamePage = getPref(FirebugPrefDomain, showLinksToOtherPartsOfSamePagePref);
	    	
	    	for (var i = 0; i < documentList.length; i++) {
	    		
		    	var allElements = documentList[i].getElementsByTagName("a");

				for (var j = 0; j < allElements.length; j++) {
					element = allElements[j];
					if (element.nodeType === 1) {
						hrefAttr = element.getAttribute("href");
						
						if (hrefAttr) {
							absoluteHref = element.href;

							if (this.checkLinkToWebPageRule(absoluteHref, showOnlyLinksToWebPages) &&
									this.checkLinkToPartOfSamePageRule(hrefAttr, showLinksToOtherPartsOfSamePage)) {
								relAttr = element.getAttribute("rel");
								textAttr = element.innerHTML;
								titleAttr = element.getAttribute("title");
								if (!titleAttr) titleAttr = '';
								
								external = this.getExternal(hrefAttr);
								absolute = this.getAbsolute(hrefAttr);
								
								link = new Link(hrefAttr, absoluteHref, relAttr, textAttr, titleAttr, external, absolute);
								linkList.push(link);
								
							}
							
						}
					}
				}
	    		
	    	}
	    	
			return linkList;
	    },
	    
	    checkLinkToWebPageRule: function(href, showOnlyLinksToWebPages) {
	    	if (!showOnlyLinksToWebPages) {
	    		return true;
	    	}
	    	
	    	var url = href.toLowerCase();
	    	
	    	if (url.indexOf('http') != 0) {
	    		return false;
	    	} else {
	    		return true;
	    	}
	    },
	    
	    checkLinkToPartOfSamePageRule: function(href, showLinksToOtherPartsOfSamePage) {
	    	if (showLinksToOtherPartsOfSamePage) {
	    		return true;
	    	}
	    	
	    	var url = href.toLowerCase();
	    	
	    	if (url.indexOf('#') == 0) {
	    		return false;
	    	} else {
	    		return true;
	    	}
	    },
	    
		getExternal: function(href) {
	        var browserURI = FirebugChrome.getCurrentURI();
	        
	        var browserDomain = this.extractDomain(browserURI.spec);
	        var hrefDomain = this.extractDomain(href);
	        
	        if (href.indexOf('http') == 0 && hrefDomain.indexOf(browserDomain) != 0) {
	        	return true;
	        } else {
	        	return false;
	        }
		},
	    
		getAbsolute: function(href) {
	        if (href.indexOf('http') == 0) {
	        	return true;
	        } else {
	        	return false;
	        }
		},
		
		extractDomain: function(url) {
			if (url.indexOf('http') == 0) {
				return url.match(/:\/\/(.[^/]+)/)[1];
			}
			return url;
		}

		});
		
		
		function Link (href, absoluteHref, rel, text, title, external, absolute) {
		    this.href = href;
		    this.absoluteHref = absoluteHref;
		    this.nofollow = checkNofollow(rel); 
		    this.text = checkText(text);
		    this.title = title;
		    this.external = external;
		    this.absolute = absolute;
		}

		Link.prototype =
		/** @lends Link */
		{
		    href: null,
		    absoluteHref: null,
		    nofollow: null, 
		    text: null, 
		    title: null,
		    external: null,
		    absolute: null
		};  
		
		// Link helpers
		function checkNofollow(rel) {
			if (rel) {
				return rel.toLowerCase().indexOf('nofollow') >= 0;
			}
			return false;
		}
		
		function checkText(text) {
			return text;
		}
		
		    

	//Object with all rep templates.
	var Templates = Firebug.FireLinkReportModel.Templates = {}; 

	Templates.Rep = domplate(Firebug.Rep,
			{
			    getContextMenuItems: function(link, target, context)
			    {
			        // xxxHonza not sure how to do this better if the "copy"
			        // command shouldn't be there.
			        var popup = $("fbContextMenu");
			        FBL.eraseNode(popup);
			    }
			}); 
	
	/**
	* @class Represents a domplate template for a link entry in the link list.
	*/
	Templates.LinkRow = domplate(Templates.Rep,
	/** @lends Templates.LinkRow */
	{
	inspectable: false,
	
	linkTag:
	    FOR("link", "$links",
	        TR({"class": "linkRow", _repObject: "$link"}, 
	
	      	  TD({"class": "linkUrlCol linkCol", _repObject: "$link", onclick: "$onClickLink"},
	                DIV({"class": "linkUrlLabel linkLabel"}, "$link|getUrl"),
	                DIV({"class": "linkFullUrlLabel linkUrlLabel linkLabel sortable"}, "$link|getFullUrl")
	      	  ),
	          TD({"class": "linkTextCol linkCol"},
	                DIV({"class": "linkTextLabel linkLabel sortable"}, "$link|getText")
	          ),
	          TD({"class": "linkTitleCol linkCol"},
		            DIV({"class": "linkTitleLabel linkLabel sortable"}, "$link|getTitle")
		      ),
	          TD({"class": "linkNofollowCol linkCol"},
			        DIV({"class": "linkNofollowLabel linkLabel sortable"}, "$link|getNofollow")
			  ),
	          TD({"class": "linkExternalCol linkCol"},
				    DIV({"class": "linkExternalLabel linkLabel sortable"}, "$link|getExternal")
	          ),
		      TD({"class": "linkAbsoluteCol linkCol"},
		    		DIV({"class": "linkAbsoluteLabel linkLabel sortable"}, "$link|getAbsolute")
		      )
	        )
	    ),
	
	getUrl: function(link) {
		if (showAllLinksAsAbsolute()) {
			return link.absoluteHref;
		} else {
			return link.href;
		}
	},
	
	getFullUrl: function(link) {
	    return link.absoluteHref;
	},

	getText: function(link) {
	    return link.text;
	},
	
	getTitle: function(link) {
	    return link.title;
	},
	
	getNofollow: function(link) {
	    return link.nofollow === true ? $FLR_STR('firelinkreport.nofollow') : '';
	},

	getExternal: function(link) {
	    return link.external === true ? $FLR_STR('firelinkreport.external') : '';
	},

	getAbsolute: function(link) {
	    return link.absolute === true ? $FLR_STR('firelinkreport.absolute') : '';
	},

    // Firebug rep support
    supportsObject: function(link)
    {
        return link instanceof Link;
    },

    browseObject: function(link, context)
    {
        return false;
    },

    getRealObject: function(link, context)
    {
        return link;
    }, 
    
	getContextMenuItems: function(link, target, context)
	{
	    Templates.Rep.getContextMenuItems.apply(this, arguments);
	
	    var items = [];
	
	    items.push({
	      label: $FLR_STR("firelinkreport.showOnPage"),
	      nol10n: true,
	      command: bindFixed(this.onShowOnPage, this, context, link)
	    });
	
	    items.push({
	        label: $FLR_STR("firelinkreport.copyUrl"),
	        nol10n: true,
	        command: bindFixed(this.onCopyUrl, this, link)
	      });
	
	    items.push({
	    	label: $FLR_STR("firelinkreport.openInNewTab"),
		    nol10n: true,
		    command: bindFixed(this.onOpenInNewTab, this, link)
	    });

	    return items;
	},
	
	onCopyUrl: function(link) {
	    copyToClipboard(link.absoluteHref);
	},
	
	onShowOnPage: function(context, link) {
		var panel = context.getPanel(panelName, true);
        if (panel) {
        	panel.highlightHrefElement(link.href);
        }
	},
	
	onOpenInNewTab: function(link) {
		try {
			if (link.absoluteHref) {
				gBrowser.selectedTab = gBrowser.addTab(link.absoluteHref); 
			}
		} catch (err) {
			
		}
	},
	
	onClickLink: function(event) {
        if (isLeftClick(event))
        {
            var linkColTag = getAncestorByClass(event.target, "linkCol");
            if (linkColTag)
            {
                var link = Firebug.getRepObject(linkColTag);
                var context = Firebug.getElementPanel(linkColTag).context;
                this.onShowOnPage(context, link);
                cancelEvent(event);
            }
        } 
    }
	
	});
		
	// Menu utility
	//-----------------------------------------------------------------------------

	var MenuUtils = 
	{
	    optionMenu: function(context, label, domain, option)
	    {
	        var value = getPref(domain, option);
	        return { label: $FLR_STR(label), nol10n: true, type: "checkbox", checked: value,
	            command: bindFixed(MenuUtils.setPref, this, domain, option, !value) };
	    },

	    optionMenuAndRefresh: function(context, label, domain, option)
	    {
	        var value = getPref(domain, option);
	        return { label: $FLR_STR(label), nol10n: true, type: "checkbox", checked: value,
	            command: bindFixed(MenuUtils.setPrefAndRefresh, this, context, domain, option, !value)};
	    }, 
	    
	    setPrefAndRefresh: function(context, domain, option, value)
	    {
	        setPref(domain, option, value);

	        var panel = context.getPanel(panelName, true);
	        if (panel) {
	            panel.refresh();
	        }
	        
	        return;
	    },
	    
	    setPref: function(domain, option, value)
	    {
	    	setPref(domain, option, value);
	    }
	}; 
	
	/**
	 * @class Represents a domplate template for basic cookie list layout. This
	 *      template also included a header functionality (such a sorting).
	 */
	Templates.LinkTable = domplate(Templates.Rep,
	/** @lends Templates.LinkTable */
	{
	    inspectable: false,
	
	    tableTag:
	        TABLE({"class": "linkTable", cellpadding: 0, cellspacing: 0},
	            TBODY(
	                TR({"class": "linkHeaderRow", onclick: "$onClickHeader"},
	                    TD({id: "colUrl", "class": "linkHeaderCell alphaValue"},
	                        DIV({"class": "linkHeaderCellBox", title: $FLR_STR("firelinkreport.header.url.tooltip")}, 
	                        $FLR_STR("firelinkreport.header.url.label"))
	                    ),
	                    TD({id: "colText", "class": "linkHeaderCell alphaValue"},
	                        DIV({"class": "linkHeaderCellBox", title: $FLR_STR("firelinkreport.header.text.tooltip")}, 
	                        $FLR_STR("firelinkreport.header.text.label"))
	                    ),
	                    TD({id: "colTitle", "class": "linkHeaderCell alphaValue"},
		                    DIV({"class": "linkHeaderCellBox", title: $FLR_STR("firelinkreport.header.title.tooltip")}, 
		                    $FLR_STR("firelinkreport.header.title.label"))
		                ),
	                    TD({id: "colNofollow", "class": "linkHeaderCell alphaValue"},
			                DIV({"class": "linkHeaderCellBox", title: $FLR_STR("firelinkreport.header.nofollow.tooltip")}, 
			                $FLR_STR("firelinkreport.header.nofollow.label"))
			            ),
	                    TD({id: "colExternal", "class": "linkHeaderCell alphaValue"},
				            DIV({"class": "linkHeaderCellBox", title: $FLR_STR("firelinkreport.header.external.tooltip")}, 
				            $FLR_STR("firelinkreport.header.external.label"))
				        ),
	                    TD({id: "colAbsolute", "class": "linkHeaderCell alphaValue"},
					        DIV({"class": "linkHeaderCellBox", title: $FLR_STR("firelinkreport.header.absolute.tooltip")}, 
					        $FLR_STR("firelinkreport.header.absolute.label"))
					    )
	                )
	            )
	        ),
	
	    onClickHeader: function(event)
	    {
	        if (!isLeftClick(event))
	            return;
	
	        var table = getAncestorByClass(event.target, "linkTable");
	        var column = getAncestorByClass(event.target, "linkHeaderCell");
	        this.sortColumn(table, column);
	    },
	
	    sortColumn: function(table, col, direction)
	    {
	        if (!col)
	            return;
	
	        if (typeof(col) == "string")
	        {
	            var doc = table.ownerDocument;
	            col = doc.getElementById(col);
	        }
	
	        if (!col)
	            return;
	
	        var numerical = !hasClass(col, "alphaValue");
	
	        var colIndex = 0;
	        for (col = col.previousSibling; col; col = col.previousSibling)
	            ++colIndex;
	
	        this.sort(table, colIndex, numerical, direction);
	    },
	
	    sort: function(table, colIndex, numerical, direction)
	    {
	        var tbody = table.lastChild;
	        var headerRow = tbody.firstChild;
	
	        // Remove class from the currently sorted column
	        var headerSorted = getChildByClass(headerRow, "linkHeaderSorted");
	        removeClass(headerSorted, "linkHeaderSorted");
	
	        // Mark new column as sorted.
	        var header = headerRow.childNodes[colIndex];
	        setClass(header, "linkHeaderSorted");
	
	        // If the column is already using required sort direction, bubble out.
	        if ((direction == "desc" && header.sorted == 1) ||
	            (direction == "asc" && header.sorted == -1))
	            return;
	
	        var values = [];
	        for (var row = tbody.childNodes[1]; row; row = row.nextSibling)
	        {
	            var cell = getChildByClass(row.childNodes[colIndex], "sortable");
	            var value = numerical ? parseFloat(cell.textContent) : cell.textContent;
	            values.push({row: row, value: value});
	        }
	
	        values.sort(function(a, b) { return a.value < b.value ? -1 : 1; });
	
	        if ((header.sorted && header.sorted == 1) || (!header.sorted && direction == "asc"))
	        {
	            removeClass(header, "sortedDescending");
	            setClass(header, "sortedAscending");
	
	            header.sorted = -1;
	
	            for (var i = 0; i < values.length; ++i)
	            {
	                tbody.appendChild(values[i].row);
	                if (values[i].info)
	                    tbody.appendChild(values[i].info);
	            }
	        }
	        else
	        {
	            removeClass(header, "sortedAscending");
	            setClass(header, "sortedDescending");
	
	            header.sorted = 1;
	
	            for (var i = values.length-1; i >= 0; --i)
	            {
	                tbody.appendChild(values[i].row);
	                if (values[i].info)
	                    tbody.appendChild(values[i].info);
	            }
	        }
	
	        // Remember last sorted column & direction in preferences.
	        var prefValue = header.getAttribute("id") + " " + (header.sorted > 0 ? "desc" : "asc");
	        setPref(FirebugPrefDomain, lastSortedColumn, prefValue);
	    },
	
	    supportsObject: function(object)
	    {
	        return (object == this) ? this : null;
	    },
	
	    // Context menu
	    getContextMenuItems: function(object, target, context)
	    {
	        Templates.Rep.getContextMenuItems.apply(this, arguments);
	
	        var items = [
	            { 
	              label: $FLR_STR("firelinkreport.header.ResetColumns"),
	              nol10n: true, 
	              command: bindFixed(this.onResetColumns, this, context)
	            }
	        ];
	
	        return items;
	    },
	
	    onResetColumns: function(context)
	    {
	        var panel = context.getPanel(panelName, true);
	        var header = getElementByClass(panel.panelNode, "linkHeaderRow");
	
	        var columns = header.childNodes;
	        for (var i=0; i<columns.length; i++)
	        {
	            var col = columns[i];
	            if (col.style)
	                col.style.width = "";
	        }
	    },
	
	    createTable: function(parentNode)
	    {
	        // Create cookie table UI.
	        var table = this.tableTag.replace({}, parentNode, this);
	
	        // Update columns width according to the preferences.
	        var header = getElementByClass(table, "linkHeaderRow");
	        var columns = header.getElementsByTagName("td");
	        for (var i=0; i<columns.length; i++)
	        {
	            var col = columns[i];
	            var colId = col.getAttribute("id");
	            if (!colId || !col.style)
	                continue;
	
	            var width = getPref(FirebugPrefDomain, "firelinkreport." + colId + ".width");
	            if (width)
	                col.style.width = width + "px";
	        }
	
	        return table;
	    },
	
	    render: function(links, parentNode)
	    {
	        // Create basic cookie-list structure.
	        var table = this.createTable(parentNode);
	        var header = getElementByClass(table, "linkHeaderRow");
	
	        var tag = Templates.LinkRow.linkTag;
	        return tag.insertRows({links: links}, header);
	    }
	}); 
	
	
	// Resizable column helper (helper for Templates.LinkTable)
		//-----------------------------------------------------------------------------

		var HeaderColumnResizer =
		{
		    resizing: false,
		    currColumn: null,
		    startX: 0,
		    startWidth: 0,
		    lastMouseUp: 0,

		    onMouseClick: function(event)
		    {
		        if (!isLeftClick(event))
		            return;

		        // Avoid click event for sorting, if the resizing has been just finished.
		        var rightNow = now();
		        if ((rightNow - this.lastMouseUp) < 1000)
		            cancelEvent(event);
		    },

		    onMouseDown: function(event)
		    {
		        if (!isLeftClick(event))
		            return;

		        var target = event.target;
		        if (!hasClass(target, "linkHeaderCellBox"))
		            return;

		        var header = getAncestorByClass(target, "linkHeaderRow");
		        if (!header)
		            return;

		        this.onStartResizing(event);

		        cancelEvent(event);
		    },

		    onMouseMove: function(event)
		    {
		        if (this.resizing)
		        {
		            if (hasClass(target, "linkHeaderCellBox"))
		                target.style.cursor = "e-resize";

		            this.onResizing(event);
		            return;
		        }

		        var target = event.target;
		        if (!hasClass(target, "linkHeaderCellBox"))
		            return;

		        if (target)
		            target.style.cursor = "";

		        if (!this.isBetweenColumns(event))
		            return;

		        // Update cursor if the mouse is located between two columns.
		        target.style.cursor = "e-resize";
		    },

		    onMouseUp: function(event)
		    {
		        if (!this.resizing)
		            return;

		        this.lastMouseUp = now();

		        this.onEndResizing(event);
		        cancelEvent(event);
		    },

		    onMouseOut: function(event)
		    {
		        if (!this.resizing)
		            return;

		        var target = event.target;
		        if (target == event.explicitOriginalTarget)
		            this.onEndResizing(event);

		        cancelEvent(event);
		    },

		    isBetweenColumns: function(event)
		    {
		        var target = event.target;
		        var x = event.clientX;
		        var y = event.clientY;

		        var column = getAncestorByClass(target, "linkHeaderCell");
		        var offset = getClientOffset(column);
		        var size = getOffsetSize(column);

		        if (column.previousSibling)
		        {
		            if (x < offset.x + 4)
		                return 1;   // Mouse is close to the left side of the column (target).
		        }

		        if (column.nextSibling)
		        {
		            if (x > offset.x + size.width - 6)
		                return 2;  // Mouse is close to the right side.
		        }

		        return 0;
		    },

		    onStartResizing: function(event)
		    {
		        var location = this.isBetweenColumns(event);
		        if (!location)
		            return;

		        var target = event.target;

		        this.resizing = true;
		        this.startX = event.clientX;

		        // Currently resizing column.
		        var column = getAncestorByClass(target, "linkHeaderCell");
		        this.currColumn = (location == 1) ? column.previousSibling : column;

		        // Last column width.
		        var size = getOffsetSize(this.currColumn);
		        this.startWidth = size.width;

		    },

		    onResizing: function(event)
		    {
		        if (!this.resizing)
		            return;

		        var newWidth = this.startWidth + (event.clientX - this.startX);
		        this.currColumn.style.width = newWidth + "px";

		    },

		    onEndResizing: function(event)
		    {
		        if (!this.resizing)
		            return;

		        this.resizing = false;

		        var newWidth = this.startWidth + (event.clientX - this.startX);
		        this.currColumn.style.width = newWidth + "px";

		        // Store width into the preferences.
		        var colId = this.currColumn.getAttribute("id");
		        if (colId)
		        {
		            var prefName = FirebugPrefDomain + ".firelinkreport." + colId + ".width";

		            // Use directly nsIPrefBranch interface as the pref
		            // doesn't have to exist yet.
		            prefs.setIntPref(prefName, newWidth);
		        }

		    }
		}; 		
		
		// Time Helpers
		//-----------------------------------------------------------------------------

		function now()
		{
		    return (new Date()).getTime();
		}
		 
		
		// Preference Helpers
		//-----------------------------------------------------------------------------

		// This functions are different in 1.05 and 1.2
		// So, this is a stable version.
		function getPref(prefDomain, name)
		{
		    var prefName = prefDomain + "." + name;

		    var type = prefs.getPrefType(prefName);
		    if (type == nsIPrefBranch.PREF_STRING)
		        return prefs.getCharPref(prefName);
		    else if (type == nsIPrefBranch.PREF_INT)
		        return prefs.getIntPref(prefName);
		    else if (type == nsIPrefBranch.PREF_BOOL)
		        return prefs.getBoolPref(prefName);
		}

		function setPref(prefDomain, name, value)
		{
		    var prefName = prefDomain + "." + name;

		    var type = prefs.getPrefType(prefName);
		    if (type == nsIPrefBranch.PREF_STRING)
		        prefs.setCharPref(prefName, value);
		    else if (type == nsIPrefBranch.PREF_INT)
		        prefs.setIntPref(prefName, value);
		    else if (type == nsIPrefBranch.PREF_BOOL)
		        prefs.setBoolPref(prefName, value);
		} 

		
		function showAllLinksAsAbsolute() {
			return getPref(FirebugPrefDomain, showAllLinksAsAbsolutePref);
		}
	
		
		// For backward compatibility with Firebug 1.1
		if (Firebug.ActivableModule)
		    Firebug.registerActivableModule(Firebug.FireLinkReportModel);
		else
		    Firebug.registerModule(Firebug.FireLinkReportModel);

		Firebug.registerPanel(FireLinkReportPanel); 		

		Firebug.registerRep(
			    Templates.LinkTable,          // Link table with list of links
			    Templates.LinkRow            // Entry in the link table
		);
	}
});


